/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.vacoor.nothing.web.dao.support.mbt;

import org.apache.ibatis.ognl.OgnlException;
import org.apache.ibatis.ognl.OgnlRuntime;
import org.apache.ibatis.ognl.PropertyAccessor;
import org.apache.ibatis.scripting.xmltags.DynamicContext;
import org.apache.ibatis.session.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
 * mybatis ognl 扩展变量绑定工具 (只能通过字符串替换方式使用(${name})
 * 主要用于全局变量的绑定,eg: 语言, 没次都传递太麻烦
 *
 * @author vacoor
 */
public abstract class MyBatis {
    private static final ThreadLocal<Map<Object, Object>> BINDER = new ThreadLocal<Map<Object, Object>>();

    /**
     * 绑定变量到当前线程的 mybatis ognl 上下文
     *
     * @param name
     * @param value
     */
    public static void bind(Object name, Object value) {
        Map<Object, Object> bindings = BINDER.get();
        if (null == bindings) {
            BINDER.set(bindings = new HashMap<Object, Object>());
        }
        bindings.put(name, value);
    }

    /**
     * 取消绑定到当前线程的 mybatis ognl 上下文的变量
     *
     * @param name
     * @param <V>
     * @return
     */
    public static <V> V unbind(Object name) {
        Map<Object, Object> bindings = BINDER.get();
        return (V) (null != bindings ? bindings.remove(name) : null);
    }

    /**
     * 获取当前线程 mybatis ognl 上下文变量
     *
     * @param key
     * @param <V>
     * @return
     */
    public static <V> V get(Object key) {
        Map<Object, Object> bindings = BINDER.get();
        return (V) (null != bindings ? bindings.get(key) : null);
    }

    // ---------------------

    static {
        // DynamicContext 静态代码块中设置了 ognl 的 PropertyAccessor
        // 访问 class 不会加载 class 执行静态代码快
        // Class<?> clazz = DynamicContext.class;
        new DynamicContext(new Configuration(), null);  // only for load
        // 代理 DynamicContext#MapContext
        for (Class<?> clazz : DynamicContext.class.getDeclaredClasses()) {
            try {
                PropertyAccessor pa = OgnlRuntime.getPropertyAccessor(clazz);
                if (null != pa) {
                    OgnlRuntime.setPropertyAccessor(clazz, new MyBatisContextAccessorProxy(pa));
                }
            } catch (OgnlException ignore) {
            }
        }
    }

    private static class MyBatisContextAccessorProxy implements PropertyAccessor {
        private final PropertyAccessor delegate;

        private MyBatisContextAccessorProxy(PropertyAccessor delegate) {
            this.delegate = delegate;
        }

        @Override
        public Object getProperty(Map ognlContext, Object obj, Object key) throws OgnlException {
            Object value = delegate.getProperty(ognlContext, obj, key);
            if (null != value) {
                return value;
            }
            return get(key);
        }

        @Override
        public void setProperty(Map map, Object o, Object o2, Object o3) throws OgnlException {
            delegate.setProperty(map, o, o2, o3);
        }
    }

    private MyBatis() {
    }
}
